package com.kaibes.core.spring;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.stereotype.Component;

import com.kaibes.core.base.proxy.ProxyHandler;
import com.kaibes.core.base.util.ClassUtils;
import com.kaibes.core.base.util.MathUtils;
import com.kaibes.core.base.util.TypeUtils;
import com.kaibes.core.spring.proxy.ProxyInvokeListener4Spring;
import com.kaibes.core.spring.proxy.ProxyLimit;

@Component
public class ApplicationUtils implements ApplicationContextAware {

    private static ApplicationUtils instance;
    private ApplicationContext applicationContext;
    
    public ApplicationUtils() {
        ApplicationUtils.instance = this;
    }
    
    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }
    
    public GenericApplicationContext getGenericApplicationContext() {
        return (GenericApplicationContext) applicationContext;
    }
    
    public void initAutowired(Object obj) {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(Autowired.class)) {
                field.setAccessible(true);
                try {
                    Object value = field.get(obj);
                    if (value == null) {
                        value = getBean(field.getType());
                        field.set(obj, value);
                    }
                } catch (Exception e) {
                }
            }
        }
    }
    
    public void initValue(Object obj) {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(Value.class)) {
                field.setAccessible(true);
                try {
                    Object result = field.get(obj);
                    if (result == null) {
                        String value = field.getAnnotation(Value.class).value();
                        Pattern pattern = Pattern.compile("\\$\\{([^:]+):([^}]*)\\}");
                        Matcher matcher = pattern.matcher(value);
                        if (matcher.matches()) {
                            Object v = getProperty(matcher.group(1), field.getType());
                            if (v == null) {
                                String d = matcher.group(2);
                                if (!d.isEmpty()) {
                                    if (Number.class.isAssignableFrom(field.getType())) {
                                        v = MathUtils.parseIntForce(d);
                                    } else {
                                        v = d;
                                    }
                                }
                            }
                            field.set(obj, v);
                        }
                    }
                } catch (Exception e) {
                }
            }
        }
    }
    
    public Object getBean(String name) {
        return applicationContext.getBean(name);
    }
    
    public <T>T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }
    
    public <T> boolean containsBean(Class<T> clazz) {
        try {
            applicationContext.getBean(clazz);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    
    public <T> boolean containsBean(String name) {
        try {
            applicationContext.getBean(name);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    
    public ApplicationUtils registerSingleton(Object obj) {
        getGenericApplicationContext().getBeanFactory().registerSingleton(obj.getClass().getName(), obj);
        return this;
    }
    
    public void registerSingleton(String name, Object obj) {
        getGenericApplicationContext().getBeanFactory().registerSingleton(name, obj);
    }
    
    @SuppressWarnings("unchecked")
    public <T> T registerBean(String name, Class<T> clazz, Object... args) {
        if(applicationContext.containsBean(name)) {
            Object bean = applicationContext.getBean(name);
            if (bean.getClass().isAssignableFrom(clazz)) {
                return (T) bean;
            } else {
                throw new RuntimeException("BeanName 重复 " + name);
            }
        }
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        for (Object arg : args) {
            beanDefinitionBuilder.addConstructorArgValue(arg);
        }
        BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
        BeanDefinitionRegistry beanFactory = (BeanDefinitionRegistry) getGenericApplicationContext().getBeanFactory();
        beanFactory.registerBeanDefinition(name, beanDefinition);
        return applicationContext.getBean(name, clazz);
    }
    
    public void destroySingleton(Object bean) {
        getGenericApplicationContext().getBeanFactory().destroyBean(bean);
    }
    
    public void destroyBean(String name) {
        if (applicationContext.containsBeanDefinition(name)) {
            getGenericApplicationContext().removeBeanDefinition(name);
        } else {
            getGenericApplicationContext().getBeanFactory().destroyBean(applicationContext.getBean(name));
        }
    }
    
    public void destroyBean(Object bean) {
        String[] names = getGenericApplicationContext().getBeanNamesForType(bean.getClass());
        for (String name : names) {
            Object temp = getBean(name);
            if (temp == bean) {
                if (applicationContext.containsBeanDefinition(name)) {
                    getGenericApplicationContext().removeBeanDefinition(name);
                } else {
                    getGenericApplicationContext().getBeanFactory().destroyBean(bean);
                }
            }
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    
    public ProxyHandler<Object> getProxyHandler(Object obj) {
        return ProxyHandler.getNewInstance(obj);
    }
    public <T>T getProxy(Object obj) {
        final ProxyHandler<Object> handler = getProxyHandler(obj);
        Collection<ProxyInvokeListener4Spring> listeners = applicationContext.getBeansOfType(ProxyInvokeListener4Spring.class).values();
        for(ProxyInvokeListener4Spring listener:listeners) {
            Class<?> clazz = TypeUtils.getClass(listener.getTargetType().getType());
            if (clazz.isAssignableFrom(obj.getClass())) {
                handler.setInvokelistener(listener);
                break;
            }
        }
        return handler.getProxy();
    }
    
    public ProxyHandler<Object> getProxyHandler(Class<?> proxyClass) {
        Object obj = null;
        ProxyLimit proxyLimit = proxyClass.getAnnotation(ProxyLimit.class);
        if (proxyLimit == null) {
            String[] names = applicationContext.getBeanDefinitionNames();
            for (String name : names) {
                Object temp = applicationContext.getBean(name);
                if (ClassUtils.looksLike(temp.getClass(), proxyClass)) {
                    obj = temp;
                    break;
                }
            }
        } else {
            String beanName = proxyLimit.name();
            if (!beanName.isEmpty()) {
            	try {
            		obj = applicationContext.getBean(beanName);
            	} catch (Exception e) {
            		obj = null;
				}
            } else {
                String[] names = applicationContext.getBeanDefinitionNames();
                Class<? extends Annotation>[] annotations = proxyLimit.value();
                if (proxyLimit.annotations().length != 0) {
                    annotations = proxyLimit.annotations();
                }
                Class<?>[] interfaces = proxyLimit.interfaces();
                for (String name : names) {
                    Object temp = applicationContext.getBean(name);
                    Class<? extends Object> clazz = temp.getClass();
                    if (!ClassUtils.isAnnotationPresent(clazz, annotations)) {
                        continue;
                    }
                    if (!ClassUtils.hasInterface(clazz, interfaces)) {
                        continue;
                    }
                    if (ClassUtils.looksLike(temp.getClass(), proxyClass)) {
                        obj = temp;
                        break;
                    }
                }
            }
        }
        if (obj == null) {
        	return null;
        } else {
        	return ProxyHandler.getNewInstance(obj, proxyClass);
        }
    }
    public <T>T getProxy(Class<T> proxyClass) {
        ProxyHandler<Object> handler = getProxyHandler(proxyClass);
        Collection<ProxyInvokeListener4Spring> listeners = applicationContext.getBeansOfType(ProxyInvokeListener4Spring.class).values();
        for(ProxyInvokeListener4Spring listener:listeners) {
            Class<?> clazz = TypeUtils.getClass(listener.getTargetType().getType());
            if (clazz.isAssignableFrom(proxyClass)) {
                handler.setInvokelistener(listener);
                break;
            }
        }
        return handler.getProxy();
    }
    
    public ProxyHandler<Object> getProxyHandler(Type proxyType) {
        Object obj = null;
        Class<?> proxyClass = TypeUtils.getClass(proxyType);
        ProxyLimit proxyLimit = proxyClass.getAnnotation(ProxyLimit.class);
        if (proxyLimit == null) {
            String[] names = applicationContext.getBeanDefinitionNames();
            for (String name : names) {
                Object temp = applicationContext.getBean(name);
                String className = temp.getClass().getName();
                if (className.startsWith("com.sun.proxy.$Proxy")) {
                    try {
                        if (TypeUtils.looksLike(Class.forName(name), proxyType)) {
                            obj = temp;
                            break;
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                } else {
                    if (TypeUtils.looksLike(temp.getClass(), proxyType)) {
                        obj = temp;
                        break;
                    }
                }
            }
        } else {
            String beanName = proxyLimit.name();
            if (!beanName.isEmpty()) {
            	try {
            		obj = applicationContext.getBean(beanName);
            	} catch (Exception e) {
            		obj = null;
				}
            } else {
                Class<? extends Annotation>[] annotations = proxyLimit.value();
                if (proxyLimit.annotations().length != 0) {
                    annotations = proxyLimit.annotations();
                }
                Class<?>[] interfaces = proxyLimit.interfaces();
                String[] names = applicationContext.getBeanDefinitionNames();
                for (String name : names) {
                    Object temp = applicationContext.getBean(name);
                    Class<? extends Object> clazz = temp.getClass();
                    if (!ClassUtils.isAnnotationPresent(clazz, annotations)) {
                        continue;
                    }
                    if (!ClassUtils.hasInterface(clazz, interfaces)) {
                        continue;
                    }
                    if (TypeUtils.looksLike(temp.getClass(), proxyType)) {
                        obj = temp;
                        break;
                    }
                }
            }
        }
        if (obj == null) {
        	return null;
        } else {
        	return ProxyHandler.getNewInstance(obj, proxyClass);
        }
    }
    public <T>T getProxy(Type proxyType) {
        ProxyHandler<Object> handler = getProxyHandler(proxyType);
        Collection<ProxyInvokeListener4Spring> listeners = applicationContext.getBeansOfType(ProxyInvokeListener4Spring.class).values();
        for(ProxyInvokeListener4Spring listener:listeners) {
            Type type = listener.getTargetType().getType();
            if (type == proxyType || TypeUtils.getClass(type).isAssignableFrom(TypeUtils.getClass(proxyType))) {
                handler.setInvokelistener(listener);
                break;
            }
        }
        return handler.getProxy();
    }
    
    public Object getBeanLooksLike(Class<?> likeClass) {
        String[] names = applicationContext.getBeanDefinitionNames();
        for (String name : names) {
            Object temp = applicationContext.getBean(name);
            if (ClassUtils.looksLike(temp.getClass(), likeClass)) {
                return temp;
            }
        }
        return null;
    }
    
    public Object getBeanLooksLike(Type likeType) {
        String[] names = applicationContext.getBeanDefinitionNames();
        for (String name : names) {
            Object temp = applicationContext.getBean(name);
            if (TypeUtils.looksLike(temp.getClass(), likeType)) {
                return temp;
            }
        }
        return null;
    }
 
    public <T> T getProperty(String key, Class<T> type) {
        return applicationContext.getEnvironment().getProperty(key, type);
    }
    
    public void close(long millis) {
        try {
            Thread.sleep(millis);
        }
        catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
        }
        getGenericApplicationContext().close();
    }
    
    public static ApplicationUtils getInstance() {
        return ApplicationUtils.instance;
    }
}
